﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using InvariantWM.NormalizerUtils;

namespace InvariantWM
{
    /// <summary>
    /// Clase estatica que implementa las funciones de normalización de imágenes.
    /// </summary>
    /// 
   
   
    public class Normalizer
    {
     
        private Bitmap m_bitmap;
        private float m_xOutScaleFactor=0;
        private int m_xsize, m_ysize;
        private float m_yOutScaleFactor = 0;
        private double m_normalizingAngle = 0;
        private Point2D m_normalizingCenter;

        public Normalizer(Bitmap b){
            this.m_bitmap = b;
            m_xsize = b.Width;
            m_ysize = b.Height;
        }
        
        /// <summary>
        /// Normaliza en escala una imagen
        /// </summary>
        /// <param name="inBitmap">La imagen a normalizar</param>
        /// <param name="area">(podria ser inBitmap.Height * inBitmap.Width) El area que se desea para la imagen de salida normalizada</param>
        /// <returns></returns>
        public Bitmap scaleNormalize(int area) 
        {

            /*  Sean lx y ly las dimensiones en x e y de la imagen.
                El aspec-ratio es un numero que indica la proporcion entre ambos, se define como ly/lx
                La normalización pasa por escalar la imagen de forma que tenga aspec-ratio = 1, y un area definida (por nosotros).
                Tenemos entonces, por una parte la imagen original, de lados lx y ly y area lx*ly.
                Tendriamos también por otra, la imagen normalizada, de lados reescaldos por factores a y b, a*lx y b*ly.
                Hay que conseguir que a*lx/b*ly=1...
                Dada la expresión que se expone para el calculo de los momentos.. y los momentos de orden cero de la imagen original m00 y beta de la normalizada,
                pues operando se obtiene la expresión:
                    beta=a*b*m00, donde podemos expresar beta a partir de las areas de las imagenes
                    beta=area/areaoriginal*m00
                ->Parece ser que esa "area" la podemos elegir como queramos y obtenemos así beta...
                Podemos obtener a y b con las expresiones:

                a=sqrt(beta*aspecRatioOriginal/m00)
                b=sqrt(beta/m00*aspecRatioOriginal)

                Y reescalar siguiendo esa proporción...
             */
            Bitmap inBitmap = this.m_bitmap;

            long originalArea = inBitmap.Height * inBitmap.Width;
            float aspecRatio = (float)inBitmap.Height / (float)inBitmap.Width;

            double m00 = calculate00Momentum(inBitmap);
            //beta=area/areaoriginal*m00
            double beta=(area/originalArea)*m00;

            float a = (float)System.Math.Sqrt((double)((beta*aspecRatio)/m00));
            float b = (float)System.Math.Sqrt((double)(beta / (m00 * aspecRatio)));

            //reescalo la imagen con las a y b obtenidas.
            System.Drawing.Bitmap outBitmap = (System.Drawing.Bitmap)inBitmap.Clone();
            outBitmap = Image2DTransform.scaleImage(outBitmap,a,b);

            this.m_xOutScaleFactor = a;
            this.m_yOutScaleFactor = b;
            return outBitmap;
        }
      
        /// <summary>
        /// Calcula el momento 00 del canal rojo de la imagen- asumiendo que es una imagen de grises.
        /// </summary>
        /// <param name="inBitmap"></param>
        /// <returns></returns>
        private static double calculate00Momentum(Bitmap inBitmap) {
            double aux = 0;
            for (int i = 0; i < inBitmap.Width; i++)
                for (int j = 0; j < inBitmap.Height; j++) {
                    aux += (inBitmap.GetPixel(i, j)).R;
                }
            return aux;
        }

        private static double calculateMomentum(Bitmap inBitmap,int p, int q)
        {
            double aux = 0;
            for (int i = 0; i < inBitmap.Width; i++)
                for (int j = 0; j < inBitmap.Height; j++)
                {
                    aux +=  (Math.Pow(i, p) * Math.Pow(j, q) * (inBitmap.GetPixel(i, j)).R);
                }
            return aux;
        }

        /// <summary>
        /// Normalizacion inversa en escala de una imagen
        /// </summary>
        /// <param name="inBitmap">La imagen a normalizar</param>
        /// <returns></returns>
        public Bitmap inverseScaleNormalize(Bitmap inBitmap)
        {
            System.Drawing.Bitmap outBitmap = Image2DTransform.scaleImage(inBitmap,this.m_xsize,this.m_ysize);
            return outBitmap;
        }


        public  Bitmap rotationNormalize()
        {
            /*
             *Se definen 2 tensores, t1 y t2
             * t2=mu12 + mu30
             * t2=mu21 + mu03
             * 
             */
            
            
            Bitmap inBitmap = Image2DTransform.createPatch(m_bitmap);

            double m12 = calculateCentralMomentum(inBitmap, 1, 2);
            double m30 = calculateCentralMomentum(inBitmap, 3, 0);
            double m21 = calculateCentralMomentum(inBitmap, 2, 1);
            double m03 = calculateCentralMomentum(inBitmap, 0, 3);

            

            double t1 = m12 + m30;
            double t2 =  m21 + m03;

            //El ángulo de normalización podría ser este:
            double Phi;
            if (t2 == 0) 
            {
                Phi = 0; 
            } else 
            {
                Phi = Math.Atan(-t1 / t2);
            }
            

            //o bien él, sumandole PI radianes
            //para averiguar cual es de verdad me invento este otro tensor
            double t3 = -t1 * Math.Sin(Phi) + t2 * Math.Cos(Phi);

            if (t3 < 0)
            {
                Phi = Phi + Math.PI;
            }

            //Roto ahora la imagen en dicho angulo:? medido desde el centroide?
            //-----------------------------

            double m20 =calculateCentralMomentum(inBitmap, 2, 0);
            double m02 =calculateCentralMomentum(inBitmap, 0, 2);
            double m11= calculateCentralMomentum(inBitmap, 1, 1);
            double Phic = 0.5F * (Math.Atan(2 * m11 / (m20 - m02)));

            this.m_normalizingCenter = calculateCentroid(inBitmap);
            this.m_normalizingAngle = Phi;

            System.Drawing.Bitmap outBitmap = Image2DTransform.rotateImage3(m_bitmap, Phi, m_normalizingCenter);
            //return outBitmap;
            return outBitmap;
        
        }
      
        /// <summary>
        /// Calcula el momento central de la imagen mu(p,q)
        /// </summary>
        /// <param name="inBitmap">Imagen</param>
        /// <param name="p">primer parametro</param>
        /// <param name="q">segundo parámetro</param>
        /// <returns></returns>
        private static double calculateCentralMomentum(Bitmap inBitmap, int p, int q)
        {
            double m00 = calculateMomentum(inBitmap, 0, 0);
            double xCentroid = calculateMomentum(inBitmap, 1, 0) / m00;
            double yCentroid = calculateMomentum(inBitmap, 0, 1) / m00;

            double aux=0;
            double aux2;
            for (int i = 0; i < inBitmap.Width; i++)
            {
                aux2 = Math.Pow((i - xCentroid), p);
                for (int j = 0; j < inBitmap.Height; j++)
                {
                    aux += Math.Pow((j - yCentroid), q) * aux2 * inBitmap.GetPixel(i, j).R;
                }
            }
            return aux;
        }

        private static Point2D calculateCentroid(Bitmap inBitmap) {
            double m00 = Normalizer.calculateMomentum(inBitmap, 0, 0);
            double xCentroid = calculateMomentum(inBitmap, 1, 0) / m00;
            double yCentroid = calculateMomentum(inBitmap, 0, 1) / m00;
           return new Point2D(xCentroid, yCentroid);
        }
        public Bitmap inverseRotationNormalize(Bitmap inBitmap)
        {
            System.Drawing.Bitmap outBitmap = Image2DTransform.rotateImage3(inBitmap, m_normalizingAngle ,m_normalizingCenter); ;
            return outBitmap;
        }


    }
}
